1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 module hip.graphics.g2d.spritebatch; 12 import hip.graphics.mesh; 13 import hip.graphics.orthocamera; 14 import hip.hiprenderer.renderer; 15 import hip.assets.texture; 16 import hip.hiprenderer.framebuffer; 17 import hip.error.handler; 18 import hip.hiprenderer.shader; 19 import hip.config.renderer; 20 public import hip.api.graphics.batch; 21 public import hip.api.graphics.color; 22 public import hip.api.renderer.shaders.spritebatch; 23 24 25 /** 26 * The spritebatch contains 2 shaders. 27 * One shader is entirely internal, which you don't have any control, this is for actually being able 28 * to draw stuff on the screen. 29 * 30 * The another one is a post processing shader, which the spritebatch doesn't uses by default. If 31 * setPostProcessingShader() 32 */ 33 class HipSpriteBatch : IHipBatch 34 { 35 index_t maxQuads; 36 index_t[] indices; 37 HipSpriteVertex[] vertices; 38 39 protected bool hasInitTextureSlots; 40 Shader spriteBatchShader; 41 42 ///Post Processing Shader 43 protected Shader ppShader; 44 protected HipFrameBuffer fb; 45 protected HipTextureRegion fbTexRegion; 46 protected float managedDepth = 0; 47 48 HipOrthoCamera camera; 49 Mesh mesh; 50 51 protected IHipTexture[] currentTextures; 52 int usingTexturesCount; 53 54 uint lastDrawQuadsCount = 0; 55 uint quadsCount; 56 57 58 ShaderVar* uMVP, uTex; 59 60 61 this(HipOrthoCamera camera = null, index_t maxQuads = DefaultMaxSpritesPerBatch) 62 { 63 import hip.hiprenderer.initializer; 64 import hip.util.conv:to; 65 ErrorHandler.assertLazyExit(index_t.max > maxQuads * 6, "Invalid max quads. Max is "~to!string(index_t.max/6)); 66 this.maxQuads = maxQuads; 67 vertices = new HipSpriteVertex[maxQuads*4]; //XYZ -> 3, RGBA -> 4, ST -> 2, TexID 3+4+2+1=10 68 currentTextures = new IHipTexture[](HipRenderer.getMaxSupportedShaderTextures()); 69 usingTexturesCount = 0; 70 71 72 this.spriteBatchShader = newShader(HipShaderPresets.SPRITE_BATCH); 73 spriteBatchShader.addVarLayout(ShaderVariablesLayout.from!(HipSpriteVertexUniform)(HipRenderer.getInfo)); 74 spriteBatchShader.addVarLayout(ShaderVariablesLayout.from!(HipSpriteFragmentUniform)(HipRenderer.getInfo)); 75 spriteBatchShader.setBlending(HipBlendFunction.SRC_ALPHA, HipBlendFunction.ONE_MINUS_SRC_ALPHA, HipBlendEquation.ADD); 76 77 mesh = new Mesh(HipVertexArrayObject.getVAO!HipSpriteVertex, spriteBatchShader); 78 mesh.createVertexBuffer(cast(index_t)(maxQuads*HipSpriteVertex.quadCount), HipResourceUsage.Dynamic); 79 mesh.setIndices(HipRenderer.getQuadIndexBuffer(maxQuads)); 80 81 spriteBatchShader.useLayout.Cbuf; 82 // spriteBatchShader.bind(); 83 // spriteBatchShader.sendVars(); 84 mesh.sendAttributes(); 85 86 87 spriteBatchShader.useLayout.Cbuf; 88 spriteBatchShader.bind(); 89 spriteBatchShader.sendVars(); 90 91 uMVP = mesh.shader.get("Cbuf1.uMVP", ShaderTypes.vertex); 92 // uTex = mesh.shader.get("Cbuf.uTex", ShaderTypes.fragment); 93 94 if(camera is null) 95 camera = new HipOrthoCamera(); 96 this.camera = camera; 97 mesh.setVertices(vertices); 98 // vertices = cast(HipSpriteVertex[])mesh.vao.VBO.getBuffer(); 99 setTexture(HipTexture.getPixelTexture()); 100 101 } 102 void setCurrentDepth(float depth){managedDepth = depth;} 103 104 void setShader(Shader s) 105 { 106 if(fb is null) 107 { 108 Viewport v = HipRenderer.getCurrentViewport; 109 fb = HipRenderer.newFrameBuffer(cast(int)v.width, cast(int)v.height); 110 // fbTexRegion = new HipTextureRegion(fb.getTexture()); 111 } 112 this.ppShader = s; 113 } 114 115 /** 116 * Sets the texture slot/index for the current quad and points it to the next quad 117 */ 118 void addQuad(void[] quad, int slot) 119 { 120 if(quadsCount+1 > maxQuads) 121 flush(); 122 123 size_t start = quadsCount*4; 124 import core.stdc.string; 125 HipSpriteVertex* v = cast(HipSpriteVertex*)vertices.ptr + start; 126 memcpy(v, quad.ptr, HipSpriteVertex.sizeof * 4); 127 setTID(v[0..4], slot); 128 129 quadsCount++; 130 } 131 132 void addQuads(void[] quadsVertices, int slot) 133 { 134 import hip.util.array:swapAt; 135 assert(quadsVertices.length % (HipSpriteVertex.sizeof*4) == 0, "Count must be divisible by HipSpriteVertex.sizeof*4"); 136 HipSpriteVertex[] v = cast(HipSpriteVertex[])quadsVertices; 137 uint countOfQuads = cast(uint)(v.length / 4); 138 139 while(countOfQuads > 0) 140 { 141 size_t remainingQuads = this.maxQuads - this.quadsCount; 142 if(remainingQuads == 0) 143 { 144 flush(); 145 this.usingTexturesCount = 1; 146 swapAt(this.currentTextures, 0, slot);//Guarantee the target slot is being used 147 remainingQuads = this.maxQuads; 148 } 149 size_t quadsToDraw = (countOfQuads < remainingQuads) ? countOfQuads : remainingQuads; 150 151 152 size_t start = quadsCount*4; 153 size_t end = start + quadsToDraw*4; 154 155 156 vertices[start..end] = v[0..quadsToDraw*4]; 157 static if(!GLMaxOneBoundTexture) 158 { 159 for(int i = 0; i < quadsToDraw; i++) 160 setTID(vertices[start+i*4..$], slot); 161 162 } 163 164 v = v[quadsToDraw*4..$]; 165 166 this.quadsCount+= quadsToDraw; 167 168 169 countOfQuads-= quadsToDraw; 170 } 171 } 172 173 private int getNextTextureID(IHipTexture t) 174 { 175 for(int i = 0; i < usingTexturesCount; i++) 176 if(currentTextures[i] is t) 177 return i; 178 if(usingTexturesCount < currentTextures.length) 179 { 180 currentTextures[usingTexturesCount] = t; 181 return usingTexturesCount++; 182 } 183 return -1; 184 } 185 /** 186 * Sets the current texture in use on the sprite batch and returns its slot. 187 */ 188 protected int setTexture (IHipTexture texture) 189 { 190 int slot = getNextTextureID(texture); 191 if(slot == -1) 192 { 193 flush(); 194 slot = getNextTextureID(texture); 195 } 196 return slot; 197 } 198 protected int setTexture(IHipTextureRegion reg){return setTexture(reg.getTexture());} 199 200 protected static bool isZeroAlpha(void[] vertices) 201 { 202 HipSpriteVertex[] v = cast(HipSpriteVertex[])vertices; 203 return v[0].vColor.a == 0 && v[1].vColor.a == 0 && v[2].vColor.a == 0 && v[3].vColor.a == 0; 204 } 205 206 void draw(IHipTexture t, ubyte[] vertices) 207 { 208 if(isZeroAlpha(vertices)) return; 209 ErrorHandler.assertExit(t.getWidth != 0 && t.getHeight != 0, "Tried to draw 0 bounds sprite"); 210 int slot = setTexture(t); 211 ErrorHandler.assertExit(slot != -1, "HipTexture slot can't be -1 on draw phase"); 212 213 if(vertices.length == HipSpriteVertex.sizeof * 4) 214 addQuad(vertices, slot); 215 else 216 addQuads(vertices, slot); 217 } 218 219 void draw(IHipTexture texture, int x, int y, int z = 0, in HipColor color = HipColor.white, float scaleX = 1, float scaleY = 1, float rotation = 0) 220 { 221 import hip.global.gamedef; 222 if(color.a == 0) return; 223 if(quadsCount+1 > maxQuads) 224 flush(); 225 if(texture is null) 226 texture = cast()getDefaultTexture(); 227 228 int width = texture.getWidth(), height = texture.getHeight(); 229 ErrorHandler.assertExit(width != 0 && height != 0, "Tried to draw 0 bounds texture"); 230 int slot = setTexture(texture); 231 ErrorHandler.assertExit(slot != -1, "HipTexture slot can't be -1 on draw phase"); 232 233 size_t startVertex = quadsCount *4; 234 size_t endVertex = startVertex + 4; 235 236 getTextureVertices(vertices[startVertex..endVertex], slot, width, height, x,y,managedDepth,color, scaleX, scaleY, rotation); 237 quadsCount++; 238 } 239 240 241 void draw(IHipTextureRegion reg, int x, int y, int z = 0, in HipColor color = HipColor.white, float scaleX = 1, float scaleY = 1, float rotation = 0) 242 { 243 if(color.a == 0) return; 244 if(quadsCount+1 > maxQuads) 245 flush(); 246 ErrorHandler.assertExit(reg.getWidth() != 0 && reg.getHeight() != 0, "Tried to draw 0 bounds region"); 247 int slot = setTexture(reg); 248 ErrorHandler.assertExit(slot != -1, "HipTexture slot can't be -1 on draw phase"); 249 size_t startVertex = quadsCount*4; 250 size_t endVertex = startVertex + 4; 251 252 getTextureRegionVertices(vertices[startVertex..endVertex], slot, reg,x,y,managedDepth,color, scaleX, scaleY, rotation); 253 quadsCount++; 254 } 255 256 257 private static void setTID(HipSpriteVertex[] vertices, int tid) 258 { 259 static if(!GLMaxOneBoundTexture) 260 { 261 vertices[0].vTexID = tid; 262 vertices[1].vTexID = tid; 263 vertices[2].vTexID = tid; 264 vertices[3].vTexID = tid; 265 } 266 } 267 268 pragma(inline, true) 269 private static Vector3[4] getBounds(float x, float y, float z, float width, float height, float scaleX = 1, float scaleY = 1) 270 { 271 width*= scaleX; 272 height*= scaleY; 273 return [ 274 Vector3(x, y, z), 275 Vector3(x+width, y, z), 276 Vector3(x+width, y+height, z), 277 Vector3(x, y+height, z), 278 ]; 279 } 280 281 pragma(inline, true) 282 private static Vector3[4] getBoundsFromRotation(float x, float y, float z, float width, float height, float rotation, float scaleX = 1, float scaleY = 1) 283 { 284 import hip.math.utils:cos,sin; 285 width*= scaleX; 286 height*= scaleY; 287 float centerX = -width/2; 288 float centerY = -height/2; 289 float x2 = x + width; 290 float y2 = y + height; 291 float c = cos(rotation); 292 float s = sin(rotation); 293 294 return [ 295 Vector3(c*centerX - s*centerY + x, c*centerY + s*centerX + y, z), 296 Vector3(c*x2 - s*centerY + x, c*centerY + s*x2 + y, z), 297 Vector3(c*x2 - s*y2 + x, c*y2 + s*x2 + y, z), 298 Vector3(c*centerX - s*y2 + x, c*y2 + s*centerX + y, z), 299 ]; 300 } 301 302 static void getTextureVertices(HipSpriteVertex[] output, int slot, int width, int height, 303 int x, int y, float z = 0, in HipColor color = HipColor.white, float scaleX = 1, float scaleY = 1, float rotation = 0) 304 { 305 Vector3[4] spritePos = void; 306 if(rotation == 0) 307 spritePos = getBounds(x,y,z,width,height,scaleX,scaleY); 308 else 309 spritePos = getBoundsFromRotation(x,y,z,width,height,rotation,scaleX,scaleY); 310 311 312 for(size_t i = 0; i < 4; i++) 313 { 314 output[i].vTexST = HipTextureRegion.defaultVerticesV[i]; 315 output[i].vColor = color; 316 output[i].vPosition = spritePos[i]; 317 static if(!GLMaxOneBoundTexture) 318 output[i].vTexID = slot; 319 } 320 } 321 322 static void getTextureRegionVertices(HipSpriteVertex[] output, int slot, IHipTextureRegion reg, 323 int x, int y, float z = 0, in HipColor color = HipColor.white, float scaleX = 1, float scaleY = 1, float rotation = 0) 324 { 325 int width = reg.getWidth(); 326 int height = reg.getHeight(); 327 float[8] uvVertices = reg.getVertices(); 328 329 Vector3[4] spritePos = rotation == 0 ? getBounds(x,y,z,width,height,scaleX,scaleY) :getBoundsFromRotation(x,y,z,width,height,rotation,scaleX,scaleY); 330 331 for(size_t i = 0; i < 4; i++) 332 { 333 output[i].vTexST = Vector2(uvVertices[i*2], uvVertices[i*2+1]); 334 output[i].vColor = color; 335 output[i].vPosition = spritePos[i]; 336 static if(!GLMaxOneBoundTexture) 337 output[i].vTexID = slot; 338 } 339 } 340 341 342 343 void draw() 344 { 345 346 if(quadsCount - lastDrawQuadsCount != 0) 347 { 348 for(int i = usingTexturesCount; i < currentTextures.length; i++) 349 currentTextures[i] = currentTextures[0]; 350 mesh.bind(); 351 352 uMVP.set(camera.getMVP(), true); 353 354 // mesh.shader.setFragmentVar(uTex, currentTextures); 355 mesh.shader.bindArrayOfTextures(currentTextures, "uTex"); 356 mesh.shader.sendVars(); 357 358 size_t start = lastDrawQuadsCount*4; 359 size_t end = quadsCount*4; 360 mesh.updateVertices(cast(void[])vertices[start..end],cast(int)start); 361 362 // mesh.vao.VBO.unmapBuffer(); 363 mesh.draw((quadsCount-lastDrawQuadsCount)*6, HipRendererMode.triangles, lastDrawQuadsCount*6); 364 365 ///Some operations may require texture unbinding(D3D11 Framebuffer) 366 foreach(i; 0..usingTexturesCount) 367 currentTextures[i].unbind(i); 368 mesh.unbind(); 369 // mesh.vao.VBO.getBuffer(); 370 371 } 372 lastDrawQuadsCount = quadsCount; 373 } 374 375 void flush() 376 { 377 if(ppShader !is null) 378 fb.bind(); 379 draw(); 380 lastDrawQuadsCount = quadsCount = usingTexturesCount = 0; 381 if(ppShader !is null) 382 { 383 fb.unbind(); 384 draw(fbTexRegion, 0,0 ); 385 draw(); 386 } 387 lastDrawQuadsCount = quadsCount = usingTexturesCount = 0; 388 } 389 }